package ray.surface;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import ray.Intersection;
import ray.Ray;
import ray.RayTracer;
import ray.math.Matrix4;
import ray.math.Point2;
import ray.math.Point3;
import ray.math.Vector3;


public class Mesh extends Surface{

  /** The number of vertices in the mesh * */
  protected int numVertices;

  /** The number of triangles in the mesh * */
  protected int numTriangles;

  /** The vertex array -- always present in each mesh * */
  protected TriangleMesh [] triangles;

  /** The normal coordinate array -- may be null * */
  protected float[] normals;
  protected float[] verts;
  protected float[] texcoords;
  
  /**
   * Default constructor creates an empty mesh
   */
  public Mesh() { }

  /**
   * Basic constructor. Sets mesh data array into the mesh structure. IMPORTANT:
   * The data array are not copies so changes to the input data array will
   * affect the mesh structure. The number of vertices and the number of
   * triangles are inferred from the lengths of the verts and tris array. If
   * either is not a multiple of three, an error is thrown.
   *
   * @param verts the vertex data
   * @param tris the triangle data
   * @param normals the normal data
   */
  public Mesh(float[] verts, int[] tris, float[] normals, float[] texcoords) {

    // check the inputs
    if (verts.length % 3 != 0)
      throw new Error("Vertex array for a triangle mesh is not a multiple of 3.");
    if (tris.length % 3 != 0)
      throw new Error("Triangle array for a triangle mesh is not a multiple of 3.");

    // Set data
    setMeshData(verts, tris, normals, texcoords);

  }
  public boolean existsNormals() {
	  return normals!=null;
  }
  public boolean existsTexture() {
	  return texcoords!=null;
  }
  public Point3 getVertex(int index) {
	  return new Point3(verts[3*index],verts[3*index+1],verts[3*index+2]);
  }
  public Vector3 getNormal(int index) {
	  return new Vector3(normals[3*index], normals[3*index+1],normals[3*index+2]);
  }
  public Point2 getTexcoords(int index) {
	  return new Point2(texcoords[2*index], texcoords[2*index+1]);
  }
  @Override
  public void setTransformation(Matrix4 cMat, Matrix4 cMatInv, Matrix4 cMatTInv) {
      objToWorldCoords = Group.tmp;
      worldToObjCoords = cMatInv;
      objToWorldCoordsTransposeInv = cMatTInv;
      for (TriangleMesh triangle : triangles) {
          triangle.setTransformation(cMat, cMatInv, cMatTInv);
      }
      computeBoundingBox();
  }
  
  /**
   * Sets the mesh data and builds the triangle array.
   * @param verts the vertices
   * @param tris the triangles
   * @param normals the normals
   */
  private void setMeshData(float[] verts, int[] tris, float[] normals, float[] textcoords) {

    /*this.verts = BufferUtil.newFloatBuffer(verts.length);
    this.triangles = BufferUtil.newIntBuffer(tris.length);
    this.normals = BufferUtil.newFloatBuffer(normals.length);
    */
    this.numVertices = verts.length / 3;
    this.numTriangles = tris.length / 3;
    this.verts = verts;
    this.normals = normals;
    this.texcoords = textcoords;
    triangles = new TriangleMesh[numTriangles];
    for(int i=0;i<numTriangles;i++) {
    	triangles[i] = new TriangleMesh(this, tris[i*3+0],tris[i*3+1],tris[i*3+2] ,shader);
    }
    
    // unused
    this.normals = normals;
    
  }

  public int getNumTriangles() {
    return numTriangles;
  }

  public int getNumVertices() {
    return numVertices;
  }

  /**
   * Set the data in this mesh to the data in fileName
   * @param fileName the name of a .msh file
   */
  public void setData(String fileName) {

    readMesh(this, RayTracer.getTestFolderPath() + fileName);

  }

  
  /**
   * Reads a .msh file into outputMesh.
   *
   * @param outputMesh the mesh to store the read data
   * @param fileName the name of the mesh file to read
   * @return the TriangleMesh from the file
   */
  public static final void readMesh(Mesh outputMesh, String fileName) {

    float[] vertices;
    int[] triangles;
    float[] normals;
    float[] texcoords;
    try {

      // Create a buffered reader for the mesh file
      BufferedReader fr = new BufferedReader(new FileReader(fileName));

      // Read the size of the file
      int nPoints = Integer.parseInt(fr.readLine());
      int nPolys = Integer.parseInt(fr.readLine());

      // Create arrays for mesh data
      vertices = new float[nPoints*3];
      triangles = new int[nPolys*3];
      normals = null;
      texcoords = null;
      
      boolean vertsRead = false;
      boolean trisRead = false;
      
      String line = fr.readLine();
      while(line != null) {
    	  if(line.equals("vertices")) {
    		  for (int i = 0; i < vertices.length; ++i) {
    			  vertices[i] = Float.parseFloat(fr.readLine());
    		  }
    		  vertsRead = true;
    	  }
    	  else if( line.equals("texcoords") ) {
    		  texcoords = new float[nPoints * 2];
    		  for (int i = 0; i < texcoords.length; ++i) {
    			  texcoords[i] = Float.parseFloat(fr.readLine());
    		  }
    	  }
    	  else if( line.equals("triangles")) {
    		  for (int i = 0; i < triangles.length; ++i) {
    			  triangles[i] = Integer.parseInt(fr.readLine());
    		  }
    		  trisRead = true;
    	  }
    	  else if( line.equals("normals")) {
    		  normals = new float[nPoints*3];
    		  for (int i = 0; i < normals.length; ++i) {
    			  normals[i] = Float.parseFloat(fr.readLine());
    		  }
    	  }
    	  line = fr.readLine();
      }
      if( !vertsRead )
    	  throw new Exception("Broken file - vertices expected");
		
      if( !trisRead )
    	  throw new Exception("Broken file - triangles expected.");

      
    }
    catch (Exception e) {
      throw new Error("Error reading mesh file: "+fileName);
    }

    //Set the data in the output Mesh
    outputMesh.setMeshData(vertices, triangles, normals, texcoords);

    System.out.println("Read mesh of " + vertices.length + " verts");
  }

	
	public boolean intersect(Intersection outRecord, Ray rayIn) {	return false; }
	public void computeBoundingBox() {	}
	
	public void appendRenderableSurfaces (ArrayList<Surface> in) {
		for (TriangleMesh triangle : triangles)
			in.add(triangle);
	}
}
